#include "ikvm_server.hpp"

#include <rfb/rfbproto.h>

#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/log.hpp>
#include <xyz/openbmc_project/Common/error.hpp>

#include <boost/crc.hpp>

namespace ikvm
{

using namespace phosphor::logging;
using namespace sdbusplus::xyz::openbmc_project::Common::Error;

Server::Server(const Args& args, Input& i, Video& v) :
    pendingResize(false), frameCounter(0), numClients(0), input(i), video(v)
{
	isFrist =6;
    std::string ip("localhost");
    const Args::CommandLine& commandLine = args.getCommandLine();
    int argc = commandLine.argc;
    log<level::WARNING>("SSS1 Server started");
    server = rfbGetScreen(&argc, commandLine.argv, video.getWidth(),
                          video.getHeight(), Video::bitsPerSample,
                          Video::samplesPerPixel, Video::bytesPerPixel);

    if (!server)
    {
        log<level::ERR>("Failed to get VNC screen due to invalid arguments");
        elog<InvalidArgument>(
            xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_NAME(""),
            xyz::openbmc_project::Common::InvalidArgument::ARGUMENT_VALUE(""));
    }

    log<level::WARNING>("SSS1 Server resize");
    framebuffer.resize(
        video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);

    server->screenData = this;
    server->desktopName = "OpenBMC IKVM";
    server->frameBuffer = framebuffer.data();
    server->newClientHook = newClient;
    server->cursor = rfbMakeXCursor(cursorWidth, cursorHeight, (char*)cursor,
                                    (char*)cursorMask);
    server->cursor->xhot = 1;
    server->cursor->yhot = 1;
    std::string serverIp = (std::string)args.getServerIp();
    if(serverIp.length() == 0)
    	rfbStringToAddr(&ip[0], &server->listenInterface);
    else
	rfbStringToAddr((char*)&serverIp[0], &server->listenInterface); 	

    rfbInitServer(server);

    log<level::WARNING>("SSS1 Server rfbMarkRectAsModified");
    rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());

    char buffer[256];
    snprintf(buffer, sizeof(buffer), "SSS1 getkeyboardPath=%s", args.getKeyboardPath().c_str());
    log<level::WARNING>(buffer);

    server->kbdAddEvent = Input::keyEvent;
    server->ptrAddEvent = Input::pointerEvent;

    processTime = (1000000 / video.getFrameRate()) - 100;

    calcFrameCRC = args.getCalcFrameCRC();
}

Server::~Server()
{
    rfbScreenCleanup(server);
}

void Server::resize()
{
    if (frameCounter > video.getFrameRate())
    {
        doResize();
    }
    else
    {
        pendingResize = true;
    }
}

void Server::run()
{
    rfbProcessEvents(server, processTime);

    if (server->clientHead)
    {
        frameCounter++;
        if (pendingResize && frameCounter > video.getFrameRate())
        {
            doResize();
            pendingResize = false;
        }
    }
}

void Server::sendFrame(int index)
{
	char* data = video.getData(index);
	rfbClientIteratorPtr it;
	rfbClientPtr cl;
	int64_t frame_crc = -1;
//	char buffer[256];
//	snprintf(buffer, sizeof(buffer), "SSS1 sendFrame() data:%02x%02x%02x%02x pendingResize:%d,datalen:%d", 
//		data[0], data[1] ,data[2],data[3],
//		pendingResize?1:0, video.getFrameSize());
//	log<level::WARNING>(buffer);
	if (!data /*|| pendingResize*/)
	{
	    return;
	}

	it = rfbGetClientIterator(server);

	while ((cl = rfbClientIteratorNext(it)))
	{
	    ClientData* cd = (ClientData*)cl->clientData;
	    rfbFramebufferUpdateMsg* fu = (rfbFramebufferUpdateMsg*)cl->updateBuf;

	    if (!cd)
	    {
	        continue;
	    }
#if 0
	    if (cd->skipFrame)
	    {
	        cd->skipFrame--;
	        continue;
	    }
#endif
	    if (!cd->needUpdate)
	    {
	        continue;
	    }
		if(isFrist)
		{
			isFrist--;
			 log<level::WARNING>("SSS1 rfbSendUpdateBuf");
		}
	    if (calcFrameCRC)
	    {
	        if (frame_crc == -1)
	        {
	            /* JFIF header contains some varying data so skip it for
	             * checksum calculation */
	            frame_crc = boost::crc<32, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF,
	                                   true, true>(data + 0x30,
	                                               video.getFrameSize() - 0x30);
	        }

	        if (cd->last_crc == frame_crc)
	        {
	            continue;
	        }

	        cd->last_crc = frame_crc;
	    }

//	    cd->needUpdate = false;

	    if (cl->enableLastRectEncoding)
	    {
	        fu->nRects = 0xFFFF;
	    }
	    else
	    {
	        fu->nRects = Swap16IfLE(1);
	    }

	    fu->type = rfbFramebufferUpdate;
	    cl->ublen = sz_rfbFramebufferUpdateMsg;
//	    log<level::WARNING>("SSS1 rfbSendUpdateBuf---1");
	    rfbSendUpdateBuf(cl);

//	    char buffer[256];
//	    snprintf(buffer, sizeof(buffer), "SSS1 sendFrame width=%d, height=%d, framesize=%d", 
//			(int)video.getWidth(),  (int)video.getHeight(),  (int)video.getFrameSize());
//	    log<level::WARNING>(buffer);

	    cl->tightEncoding = rfbEncodingTight;
	    rfbSendTightHeader(cl, 0, 0, video.getWidth(), video.getHeight());

	    cl->updateBuf[cl->ublen++] = (char)(rfbTightJpeg << 4);
	    rfbSendCompressedDataTight(cl, data, video.getFrameSize());

	    if (cl->enableLastRectEncoding)
	    {
	        rfbSendLastRectMarker(cl);
	    }
//	    log<level::WARNING>("SSS1 rfbSendUpdateBuf---2");
	    rfbSendUpdateBuf(cl);
	}

	rfbReleaseClientIterator(it);
}

void Server::clientFramebufferUpdateRequest(
    rfbClientPtr cl, rfbFramebufferUpdateRequestMsg *furMsg)
{
    ClientData *cd = (ClientData *)cl->clientData;

    if (!cd)
        return;

    // Ignore the furMsg info. This service uses full frame update always.
    (void)furMsg;
   // log<level::WARNING>("start update request");
    cd->needUpdate = true;
}

void Server::clientGone(rfbClientPtr cl)
{
    Server* server = (Server*)cl->screen->screenData;
    log<level::WARNING>("clientGone");
    delete (ClientData*)cl->clientData;
    cl->clientData = nullptr;

    if (server->numClients-- == 1)
    {
        server->input.disconnect();
        rfbMarkRectAsModified(server->server, 0, 0, server->video.getWidth(),
                              server->video.getHeight());
    }
}

enum rfbNewClientAction Server::newClient(rfbClientPtr cl)
{
    Server* server = (Server*)cl->screen->screenData;

    cl->clientData =
        new ClientData(server->video.getFrameRate(), &server->input);
    log<level::WARNING>("newClient");
    cl->clientGoneHook = clientGone;
    cl->clientFramebufferUpdateRequestHook = clientFramebufferUpdateRequest;
    if (!server->numClients++)
    {
        server->input.connect();
        server->pendingResize = false;
        server->frameCounter = 0;
    }

    return RFB_CLIENT_ACCEPT;
}

void Server::doResize()
{
    rfbClientIteratorPtr it;
    rfbClientPtr cl;

    char buffer[256];
    snprintf(buffer, sizeof(buffer), "SSS1 doResize width=%d, height=%d frameRate:%d", 
		(int)video.getWidth(), (int)video.getHeight(),(int)video.getFrameRate());
    log<level::WARNING>(buffer);

    framebuffer.resize(
        video.getHeight() * video.getWidth() * Video::bytesPerPixel, 0);

    rfbNewFramebuffer(server, framebuffer.data(), video.getWidth(),
                      video.getHeight(), Video::bitsPerSample,
                      Video::samplesPerPixel, Video::bytesPerPixel);
    rfbMarkRectAsModified(server, 0, 0, video.getWidth(), video.getHeight());

    it = rfbGetClientIterator(server);

    while ((cl = rfbClientIteratorNext(it)))
    {
        ClientData* cd = (ClientData*)cl->clientData;

        if (!cd)
        {
            continue;
        }

        // delay video updates to give the client time to resize
        cd->skipFrame = video.getFrameRate();
    }

    rfbReleaseClientIterator(it);
}

} // namespace ikvm
